package com.shallowUniverse.process.configuration;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.shallowUniverse.process.annotation.ProcessBegin;
import com.shallowUniverse.process.context.ProcessEngine;
import com.shallowUniverse.process.context.handler.ProcessHandlerService;
import com.shallowUniverse.process.context.handler.ProcessHandlerHolder;
import com.shallowUniverse.process.context.interceptor.ProcessConfigurer;
import com.shallowUniverse.process.context.sql.SqlSentenceUtil;
import com.shallowUniverse.process.context.user.ProcessUserHolder;
import com.shallowUniverse.process.context.user.ProcessUserService;
import com.shallowUniverse.process.exception.*;
import com.shallowUniverse.process.mapper.ProcessDefinitionMapper;
import com.shallowUniverse.process.mapper.ProcessNodeDefinitionMapper;
import com.shallowUniverse.process.mapper.ApprovalDetailsMapper;
import com.shallowUniverse.process.mapper.ApprovalMapper;
import com.shallowUniverse.process.service.impl.ProcessNodeDefinitionServiceImpl;
import com.shallowUniverse.process.service.impl.ProcessDefinitionServiceImpl;
import com.shallowUniverse.process.service.impl.ApprovalDetailsServiceImpl;
import com.shallowUniverse.process.service.impl.ApprovalServiceImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScannerRegistrar;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Repository;
import org.springframework.util.ObjectUtils;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author gs
 * @date create in 2021/1/17 11:05
 */
@Aspect
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@EnableConfigurationProperties(ProcessProperties.class)
@ConditionalOnClass(value = {MybatisPlusAutoConfiguration.class})
@AutoConfigureAfter({MybatisPlusAutoConfiguration.class, SqlSessionFactory.class, MybatisSqlSessionFactoryBean.class, MapperScannerRegistrar.class, MapperScan.class})
@MapperScan(value = "com.shallowUniverse.process.mapper", annotationClass = Repository.class)
public class ProcessAutoConfiguration {

    @Resource
    private ProcessProperties processProperties;
    @Resource
    private ApplicationContext context;
    @Resource
    private ProcessDefinitionMapper processDefinitionMapper;
    @Resource
    private ProcessNodeDefinitionMapper processNodeDefinitionMapper;
    @Resource
    private ApprovalMapper approvalMapper;
    @Resource
    private ApprovalDetailsMapper approvalDetailsMapper;

    @Bean
    public ProcessEngine processEngine() {
        //创建流程引擎
        ProcessEngine processEngine = new ProcessEngine();
        //初始化sql管理器
        new SqlSentenceUtil(processProperties);

        //注入流程引擎需要的关键对象
        processEngine.setProcessHandlerService(processHandlerService());
        processEngine.setProcessUserService(processUserService());

        //创建流程引擎需要的各个服务类
        ProcessDefinitionServiceImpl processDefinitionService = new ProcessDefinitionServiceImpl(processDefinitionMapper, processNodeDefinitionMapper, approvalMapper);
        ProcessNodeDefinitionServiceImpl processNodeDefinitionService = new ProcessNodeDefinitionServiceImpl(processNodeDefinitionMapper, approvalMapper);
        ApprovalServiceImpl approvalService = new ApprovalServiceImpl(approvalMapper, processEngine.getProcessUserService(), approvalDetailsMapper, processDefinitionService, processNodeDefinitionService);
        ApprovalDetailsServiceImpl approvalDetailsService = new ApprovalDetailsServiceImpl(approvalDetailsMapper, approvalService, processNodeDefinitionService, processEngine.getProcessHandlerService(), processEngine.getProcessUserService());

        //注入流程引擎需要的各个服务类
        processEngine.setProcessDefinitionService(processDefinitionService);
        processEngine.setApprovalService(approvalService);
        processEngine.setApprovalDetailsService(approvalDetailsService);
        processEngine.setProcessNodeDefinitionService(processNodeDefinitionService);

        //判断是否存在拦截器配置实现类
        Map<String, ProcessConfigurer> beansOfType = context.getBeansOfType(ProcessConfigurer.class);
        if (!ObjectUtils.isEmpty(beansOfType) && beansOfType.size() != 0) {
            beansOfType.entrySet().iterator().forEachRemaining(beanOfType -> {
                beanOfType.getValue().configurer(processEngine);
            });
        }

        return processEngine;
    }

    @Bean
    @ConditionalOnBean(MybatisSqlSessionFactoryBean.class)
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //判断是否设置了数据库类型
        if (!ObjectUtils.isEmpty(processProperties.getDbType()) && !ObjectUtils.isEmpty(DbType.getDbType(processProperties.getDbType()))) {
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.getDbType(processProperties.getDbType())));
        } else {
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        }
        return interceptor;
    }

    @Bean
    public ProcessHandlerService processHandlerService() {
        return new ProcessHandlerHolder(context, processProperties.getHandlerPackages());
    }

    @Bean
    public ProcessUserService processUserService() {
        return new ProcessUserHolder();
    }

    @Around("@annotation(processBegin)")
    public Object around(ProceedingJoinPoint joinPoint, ProcessBegin processBegin) throws ProcessNotFoundException, PrimaryKeyNotFoundException, NotImplementedException, ApprovalNotExistException, ProcessNodeDefinitionErrorException {
        return processEngine().beginProcess(joinPoint, processBegin);
    }

    @Pointcut("execution(* com.shallowUniverse.process.context.ProcessEngine.*(..)) && !execution(* com.shallowUniverse.process.context.ProcessEngine.addInterceptor()) && !execution(* com.shallowUniverse.process.context.ProcessEngine.getProcessInterceptors()) && !execution(* com.shallowUniverse.process.context.ProcessEngine.autoWriedUser())")
    public void pointCut() {

    }

    @Before("pointCut()")
    public void before() throws AuthenticationException {
        processEngine().autoWriedUser();
    }

    @AfterReturning(pointcut = "pointCut()", returning = "returnValue")
    public Object after(JoinPoint joinPoint, Object returnValue) {
        //拿到方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //执行后置处理器
        if (processEngine().getProcessInterceptors().size() != 0) {
            processEngine().getProcessInterceptors().forEach(processInterceptor -> processInterceptor.postExecuteProcessor(method, returnValue));
        }

        //释放用户
        processUserService().clear();
        //返回返回值
        return returnValue;
    }
}
